package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

import java.io.EOFException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see    nachos.vm.VMProcess
 * @see    nachos.network.NetProcess
 */
public class UserProcess {

	/**
	 * Allocate a new process.
	 */
	public UserProcess() {

		openFiles = new OpenFile[maxDesc];// set the max descriptors allowed
		openFiles[0] = UserKernel.console.openForReading();
		openFiles[1] = UserKernel.console.openForWriting();

		// The child process has a new unique
		// * process ID, and starts with stdin opened as file descriptor 0, and stdout
		// * opened as file descriptor 1.



		pidLock.acquire();
		pid = currentPID;
		currentPID++;
		processesRunning++;
		pidLock.release();


	}

	/**
	 * Allocate and return a new process of the correct class. The class name
	 * is specified by the <tt>nachos.conf</tt> key
	 * <tt>Kernel.processClassName</tt>.
	 *
	 * @return    a new process of the correct class.
	 */
	public static UserProcess newUserProcess() {
		return (UserProcess) Lib.constructObject(Machine.getProcessClassName());
	}

	/**
	 * Execute the specified program with the specified arguments. Attempts to
	 * load the program, and then forks a thread to run it.
	 *
	 * @param    name    the name of the file containing the executable.
	 * @param    args    the arguments to pass to the executable.
	 * @return    <tt>true</tt> if the program was successfully executed.
	 */
	public boolean execute(String name, String[] args) {
		if (!load(name, args)) {
			return false;
		}

		new UThread(this).setName(name).fork();

		return true;
	}

	/**
	 * Save the state of this process in preparation for a context switch.
	 * Called by <tt>UThread.saveState()</tt>.
	 */
	public void saveState() {
	}

	/**
	 * Restore the state of this process after a context switch. Called by
	 * <tt>UThread.restoreState()</tt>.
	 */
	public void restoreState() {
		Machine.processor().setPageTable(pageTable);
	}

	/**
	 * Read a null-terminated string from this process's virtual memory. Read
	 * at most <tt>maxLength + 1</tt> bytes from the specified address, search
	 * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
	 * without including the null terminator. If no null terminator is found,
	 * returns <tt>null</tt>.
	 *
	 * @param    vaddr    the starting virtual address of the null-terminated
	 *            string.
	 * @param    maxLength    the maximum number of characters in the string,
	 *                not including the null terminator.
	 * @return    the string read, or <tt>null</tt> if no null terminator was
	 *        found.
	 */
	public String readVirtualMemoryString(int vaddr, int maxLength) {
		Lib.assertTrue(maxLength >= 0);

		byte[] bytes = new byte[maxLength + 1];

		int bytesRead = readVirtualMemory(vaddr, bytes);

		for (int length = 0; length < bytesRead; length++) {
			if (bytes[length] == 0) {
				return new String(bytes, 0, length);
			}
		}

		return null;
	}

	/**
	 * Transfer data from this process's virtual memory to all of the specified
	 * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
	 *
	 * @param    vaddr    the first byte of virtual memory to read.
	 * @param    data    the array where the data will be stored.
	 * @return    the number of bytes successfully transferred.
	 */
	public int readVirtualMemory(int vaddr, byte[] data) {
		return readVirtualMemory(vaddr, data, 0, data.length);
	}

	/**
	 * Transfer data from this process's virtual memory to the specified array.
	 * This method handles address translation details. This method must
	 * <i>not</i> destroy the current process if an error occurs, but instead
	 * should return the number of bytes successfully copied (or zero if no
	 * data could be copied).
	 *
	 * @param    vaddr    the first byte of virtual memory to read.
	 * @param    data    the array where the data will be stored.
	 * @param    offset    the first byte to write in the array.
	 * @param    length    the number of bytes to transfer from virtual memory to
	 *            the array.
	 * @return    the number of bytes successfully transferred.
	 */
	public int readVirtualMemory(int vaddr, byte[] data, int offset,
		int length) {
		Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);
		Lib.debug(dbgProcess, "Storing: " + new String(data));

		//varblok++++++++++++
		byte[] memory = Machine.processor().getMemory();
		int vpn, ppn;        //virtual and physical pages
		int voffset;        //virtual offset
		int paddr;        //physical addr
		int bytesWriten = 0;    //the amount of data writen
		int bytesToWrite = data.length - offset;
		//===================


		//initialize the virtual pages
		vpn = Processor.pageFromAddress(vaddr);
		voffset = Processor.offsetFromAddress(vaddr);
		//===================

		//loop to write++++++
		while (bytesToWrite > 0) {
			//get the page nums++
			ppn = pageTable[vpn].ppn;
			pageTable[vpn].used = true;
			Lib.debug(dbgProcess, "Vaddr translatein - Vpn: " + vpn + " voffset " + voffset + " vaddr: " + vaddr);
			//===================

			//do some checks+++++
			if (vpn < 0 || vpn >= pageTable.length) { //vpn out of bounds
				Lib.debug('a', "Error: vpn from vaddr out of bounds");
				return 0;
			}
			if (voffset < 0 || voffset >= pageSize) {
				Lib.debug('a', "Error: voffset from vaddr out of bounds");
				return 0;
			}
			//===================

			//read++++++++++++++
			//make the paddr
			paddr = Processor.makeAddress(ppn, voffset);
			//write to pagelen
			int cpln = Math.min(bytesToWrite, pageSize - voffset);
			byte[] cpy = new byte[cpln];

			System.arraycopy(data, offset + bytesWriten, cpy, 0, cpln);

			Lib.debug(dbgProcess, "Writing " + new String(cpy) + " to paddr " + paddr);
			//System.arraycopy(data, offset + bytesWriten, memory, paddr, cpln);
			System.arraycopy(memory, paddr, data, offset + bytesWriten, cpln);
			//prepare for next loop
			vpn++;
			voffset = 0;
			bytesToWrite -= cpln;
			bytesWriten += cpln;
			//===================
		}
		//===================

		return bytesWriten;
	}

	/**
	 * Transfer all data from the specified array to this process's virtual
	 * memory.
	 * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
	 *
	 * @param    vaddr    the first byte of virtual memory to write.
	 * @param    data    the array containing the data to transfer.
	 * @return    the number of bytes successfully transferred.
	 */
	public int writeVirtualMemory(int vaddr, byte[] data) {
		return writeVirtualMemory(vaddr, data, 0, data.length);
	}

	/**
	 * Transfer data from the specified array to this process's virtual memory.
	 * This method handles address translation details. This method must
	 * <i>not</i> destroy the current process if an error occurs, but instead
	 * should return the number of bytes successfully copied (or zero if no
	 * data could be copied).
	 *
	 * @param    vaddr    the first byte of virtual memory to write.
	 * @param    data    the array containing the data to transfer.
	 * @param    offset    the first byte to transfer from the array.
	 * @param    length    the number of bytes to transfer from the array to
	 *            virtual memory.
	 * @return    the number of bytes successfully transferred.
	 */
	public int writeVirtualMemory(int vaddr, byte[] data, int offset,
		int length) {
		Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);
		Lib.debug(dbgProcess, "Storing: " + new String(data));

		//varblok++++++++++++
		byte[] memory = Machine.processor().getMemory();
		int vpn, ppn;        //virtual and physical pages
		int voffset;        //virtual offset
		int paddr;        //physical addr
		int bytesWriten = 0;    //the amount of data writen
		int bytesToWrite = data.length - offset;
		//===================


		//initialize the virtual pages
		vpn = Processor.pageFromAddress(vaddr);
		voffset = Processor.offsetFromAddress(vaddr);
		//===================

		//loop to write++++++
		while (bytesToWrite > 0) {
			//get the page nums++
			ppn = pageTable[vpn].ppn;
			pageTable[vpn].used = true;
			pageTable[vpn].dirty = true;
			Lib.debug(dbgProcess, "Vaddr translatein - Vpn: " + vpn + " voffset " + voffset + " vaddr: " + vaddr);
			//===================

			//do some checks+++++
			if (vpn < 0 || vpn >= pageTable.length) { //vpn out of bounds
				Lib.debug('a', "Error: vpn from vaddr out of bounds");
				return 0;
			}
			if (voffset < 0 || voffset >= pageSize) {
				Lib.debug('a', "Error: voffset from vaddr out of bounds");
				return 0;
			}
			//===================

			//write++++++++++++++
			//make the paddr
			paddr = Processor.makeAddress(ppn, voffset);
			//write to pagelen
			int cpln = Math.min(bytesToWrite, pageSize - voffset);
			byte[] cpy = new byte[cpln];
			System.arraycopy(data, offset + bytesWriten, cpy, 0, cpln);
			Lib.debug(dbgProcess, "Writing " + new String(cpy) + " to paddr " + paddr);
			System.arraycopy(data, offset + bytesWriten, memory, paddr, cpln);
			//prepare for next loop
			vpn++;
			voffset = 0;
			bytesToWrite -= cpln;
			bytesWriten += cpln;
			//===================
		}
		//===================

		return bytesWriten;
	}

	/**
	 * Load the executable with the specified name into this process, and
	 * prepare to pass it the specified arguments. Opens the executable, reads
	 * its header information, and copies sections and arguments into this
	 * process's virtual memory.
	 *
	 * @param    name    the name of the file containing the executable.
	 * @param    args    the arguments to pass to the executable.
	 * @return    <tt>true</tt> if the executable was successfully loaded.
	 */
	public boolean load(String name, String[] args) {
		Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");

		OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
		if (executable == null) {
			Lib.debug(dbgProcess, "\topen failed");
			return false;
		}

		try {
			coff = new Coff(executable);
		} catch (EOFException e) {
			executable.close();
			Lib.debug(dbgProcess, "\tcoff load failed");
			return false;
		}

		// make sure the sections are contiguous and start at page 0
		numPages = 0;
		for (int s = 0; s < coff.getNumSections(); s++) {
			CoffSection section = coff.getSection(s);
			if (section.getFirstVPN() != numPages) {
				coff.close();
				Lib.debug(dbgProcess, "\tfragmented executable");
				return false;
			}
			numPages += section.getLength();
		}

		// make sure the argv array will fit in one page
		byte[][] argv = new byte[args.length][];
		int argsSize = 0;
		for (int i = 0; i < args.length; i++) {
			argv[i] = args[i].getBytes();
			// 4 bytes for argv[] pointer; then string plus one for null byte
			argsSize += 4 + argv[i].length + 1;
		}
		if (argsSize > pageSize) {
			coff.close();
			Lib.debug(dbgProcess, "\targuments too long");
			return false;
		}

		// program counter initially points at the program entry point
		initialPC = coff.getEntryPoint();

		// next comes the stack; stack pointer initially points to top of it
		numPages += stackPages;
		initialSP = numPages * pageSize;

		// and finally reserve 1 page for arguments
		numPages++;

		if (!loadSections()) {
			return false;
		}

		// store arguments in last page
		int entryOffset = (numPages - 1) * pageSize;
		int stringOffset = entryOffset + args.length * 4;

		this.argc = args.length;
		this.argv = entryOffset;

		for (int i = 0; i < argv.length; i++) {
			byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
			Lib.assertTrue(writeVirtualMemory(entryOffset, stringOffsetBytes) == 4);
			entryOffset += 4;
			Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i])
				== argv[i].length);
			stringOffset += argv[i].length;
			Lib.assertTrue(writeVirtualMemory(stringOffset, new byte[]{0}) == 1);
			stringOffset += 1;
		}

		return true;
	}

	/**
	 * Allocates memory for this process, and loads the COFF sections into
	 * memory. If this returns successfully, the process will definitely be
	 * run (this is the last step in process initialization that can fail).
	 *
	 * @return    <tt>true</tt> if the sections were successfully loaded.
	 */
	protected boolean loadSections() {
		// IN this method we need to create page tables to physical memory

		if (numPages > Machine.processor().getNumPhysPages()) {
			coff.close();
			Lib.debug(dbgProcess, "\tinsufficient physical memory");
			return false;
		}
		//enough pages?
		if (UserKernel.freepages.size() < numPages) {
			return false;
		}

		// load sections
		pageTable = new TranslationEntry[numPages];

		int totalpagesNOTSTACK = 0;
		for (int s = 0; s < coff.getNumSections(); s++) {
			CoffSection section = coff.getSection(s);
			Lib.debug(dbgProcess, "\tinitializing " + section.getName()
				+ " section (" + section.getLength() + " pages)");
			for (int i = 0; i < section.getLength(); i++) {

				int vpn = section.getFirstVPN() + i;
				int ppn = UserKernel.freepages.removeFirst();
				pageTable[vpn] = new TranslationEntry(vpn, ppn, true, section.isReadOnly(), false, false);
				totalpagesNOTSTACK++;

				// for now, just assume virtual addresses=physical addresses
				section.loadPage(i, ppn);

			}
		}
		// testvariable was not declared-----> UserKernel.testvariable = pageTable[0].vpn;
		// Stack    8 for stack pages + 1 for arguments
		for (int i = totalpagesNOTSTACK; i < numPages; i++) {

			int vpn = i;
			int ppn = UserKernel.freepages.removeFirst();
			pageTable[vpn] = new TranslationEntry(vpn, ppn, true, false, false, false);

		}

		return true;
	}

	/**
	 * Release any resources allocated by <tt>loadSections()</tt>.
	 */
	protected void unloadSections() {

		for (int i = 0; i < numPages; i++) {
			int pageNum = pageTable[i].ppn;
			pageTable[i] = null;
			UserKernel.freepages.add(pageNum);
		}

	}

	/**
	 * Initialize the processor's registers in preparation for running the
	 * program loaded into this process. Set the PC register to point at the
	 * start function, set the stack pointer register to point at the top of
	 * the stack, set the A0 and A1 registers to argc and argv, respectively,
	 * and initialize all other registers to 0.
	 */
	public void initRegisters() {
		Processor processor = Machine.processor();

		// by default, everything's 0
		for (int i = 0; i < processor.numUserRegisters; i++) {
			processor.writeRegister(i, 0);
		}

		// initialize PC and SP according
		processor.writeRegister(Processor.regPC, initialPC);
		processor.writeRegister(Processor.regSP, initialSP);

		// initialize the first two argument registers to argc and argv
		processor.writeRegister(Processor.regA0, argc);
		processor.writeRegister(Processor.regA1, argv);
	}

	/* Halt the Nachos machine by calling Machine.halt(). Only the root process
	 * (the first process, executed by UserKernel.run()) should be allowed to
	 * execute this syscall. Any other process should ignore the syscall and return
	 * immediately.
	 */
	private int handleHalt() {
		if (getPid() == 0) {
			Machine.halt();
			Lib.assertNotReached("Machine.halt() did not halt machine!");
		}
		System.out.println("bad halt command");
		return 0;
	}

	private int handleExit(int a0) {
		System.out.println("Handling exit call... Parent = " + Parent);
		if (Parent != null) {
			System.out.println("putting in parent. a0 = " + a0 + " pid = " + pid);
			System.out.flush();
			Parent.childExitStatus.put(pid, a0);        // put the status in...
			Parent.lck.acquire();
			System.out.println("READY TO WAKE SHIT");
			System.out.flush();
			Parent.childExit.wake();
			Parent.lck.release();
		}
		if (Parent != null) {
			unloadSections();        // make sure you unload the sections...
		}
		if (coff != null) {        // close the coff (little mips programs in test folder_
			coff.close();
			coff = null;
		}
		for (OpenFile file : openFiles) {
			if (file != null) {
				file.close();
			}
		}



		pidLock.acquire();
		processesRunning--;
		pidLock.release();
		//    ThreadedKernel.fileSystem.remove(name);
		if (processesRunning == 0) {           //processRunning is a counter for how many processes running
			Kernel.kernel.terminate();     // as varick said we must terminate the kernel if
			// no processes are running...//Kernel.kernel.terminate();
			Lib.assertNotReached("terminate() did not terminate the machine!");//see if it terminantes....
		} else {
			UThread.finish();
			//finish the thread we are done with it.        // kill the thread.....
		}
		//exit ...
		// make sure you close all the files..
		System.out.println("FINISHED EXITING");
		System.out.flush();
		return a0;
	}

	/* private byte[] reverseBytes(byte[] b){             // used to make sure the most significant/least
	byte[] reversedBytes = new byte[b.length];    // significant byte is ordered properly

	for(int i=0;i<b.length; i++){
	reversedBytes[i] = b[b.length - (i+1)];
	}
	//  reversedBytes = Integer.reverseBytes()
	return reversedBytes;
	}*/
	private int handleExec(int file, int argc, int argv) {
		String fileName = readVirtualMemoryString(file, 256);
		if (fileName == null || argc < 0) { // argc must be non negative..
			return -1;                    //char *file = fileName... can't be null...
		} else {
			UserProcess p = newUserProcess();
			p.Parent = this;

			String[] args = new String[argc];


			for (int i = 0; i < argc; i++) {


				byte[] aBytes = new byte[4];
				readVirtualMemory(argv + (i * 4), aBytes);
				int argPointer = Lib.bytesToInt(aBytes, 0);

				String arg = readVirtualMemoryString(argPointer, 256);
				if (arg == null) {
					return -1;
				}
				args[i] = arg;
			}

			int newPid = p.getPid();
			children.put(newPid, p);
			boolean works = p.execute(fileName, args);
			if (!works) {
				return -1;
			} else {
				return newPid;    //returns the child process ID....
			}

		}
	}

	private int handleJoin(int process, int status2) {
		//join...
		//make hash table of child processes...
		//process ID as the key..
		//maintain table of child processes..
		// maintain another table of processes are done...
		// in handleJoin look for particular child ID and see
		// if it exists in the exit table... it has been marked as done..
		// If the child has already exited by the * time of the call, returns immediately.
		//returns =1 if processID does not refer to a child...



		if (children.size() < 1) {
			return -1;
		}
		UserProcess child = children.get(process); //a- is process ID of the child process..
		if (child == null) {
			return -1;
		}
		// if child has not exited sleep/wait


		lck.acquire();
		System.out.println("childExitStatus size is = " + childExitStatus.size());


		while (!childExitStatus.containsKey(child.getPid())) {
			childExit.sleep();

		System.out.println("HITHERE!");
		System.out.flush();
		}
		lck.release();

		Integer temp = childExitStatus.get(child.getPid());
		if (temp == null) {
			System.out.println("You Failed");
			System.out.println("childExitStatus size is = " + childExitStatus.size());

		}

		byte[] statusBytes = Lib.bytesFromInt(temp);
		writeVirtualMemory(status2, statusBytes);
		children.remove(child.getPid());            // remove so it can't be joined again..
		childExitStatus.remove(child.getPid());

		if (temp >= 0) {
			return 1;    // child exits normally
		} else {
			return 0;    //child exits because of an unhandled exception
		}

	}

	private int handleCreate(int address) {
		String fileName;
		OpenFile file;
		int descr = getFileDescriptor();

		if (descr == -1) {
			return -1;                //No spaces left
		}
		fileName = readVirtualMemoryString(address, 256);    //Load address

		/*Error checking*/
		if (fileName == null) {
			return -1;             //Address is invalid, throw error, exit
		}
		file = ThreadedKernel.fileSystem.open(fileName, true);

		if (file == null) {
			return -1;                // File could not be opened!!!
		}
		openFiles[descr] = file;
//        openFileNames[descr] = fileName;    // Error Here
		return descr;
	}

	private int handleOpen(int address) {//256 or 255
		String fileName;
		OpenFile file;
		int descr = getFileDescriptor();

		if (descr == -1) {
			return -1;                //No spaces left
		}
		fileName = readVirtualMemoryString(address, 256);    //Load address

		/*Error checking*/
		if (fileName == null) {
			return -1;             //Address is invalid, throw error, exit
		}
		file = ThreadedKernel.fileSystem.open(fileName, false);

		if (file == null) {
			return -1;                // File could not be opened!!!
		}
		openFiles[descr] = file;
		// openFileNames[descr] = fileName;        // Error Here
		return descr;
	}

	/**
	 * Attempt to read up to count bytes into buffer from the file or stream
	 * referred to by fileDescriptor.
	 *
	 * On success, the number of bytes read is returned. If the file descriptor
	 * refers to a file on disk, the file position is advanced by this number.
	 *
	 * It is not necessarily an error if this number is smaller than the number of
	 * bytes requested. If the file descriptor refers to a file on disk, this
	 * indicates that the end of the file has been reached. If the file descriptor
	 * refers to a stream, this indicates that the fewer bytes are actually
	 * available right now than were requested, but more bytes may become available
	 * in the future. Note that read() never waits for a stream to have more data;
	 * it always returns as much as possible immediately.
	 *
	 * On error, -1 is returned, and the new file position is undefined. This can
	 * happen if fileDescriptor is invalid, if part of the buffer is read-only or
	 * invalid, or if a network stream has been terminated by the remote host and
	 * no more data is available.
	 *
	 * @param fileDescriptor - a number associated with a file
	 * @param address - address where the buffer will be stored in memmory
	 * @param count - number of bytes.
	 *
	 * @return - return the number of bytes read. If there is an error, -1 will be return
	 *
	 */
	private int handleRead(int fileDescriptor, int address, int count) {
		if (fileDescriptor > (maxDesc - 1) || fileDescriptor < 0 || openFiles[fileDescriptor] == null) {
			return -1;
		}

		int amountWritten = 0; //Amount read
		int amountRead = 0;    //Amount read

		byte[] tempBuffer = new byte[count];     //temp buffer

		while (amountRead < count) {
			amountRead += openFiles[fileDescriptor].read(tempBuffer, 0, (count - amountRead));
			amountWritten += writeVirtualMemory(address, tempBuffer);
		}
		if (amountRead < count) {
			return -1;
		}

		return amountRead;
	}

	/**
	 * Attempt to write up to count bytes from buffer to the file or stream
	 * referred to by fileDescriptor. write() can return before the bytes are
	 * actually flushed to the file or stream. A write to a stream can block,
	 * however, if kernel queues are temporarily full.
	 *
	 * On success, the number of bytes written is returned (zero indicates nothing
	 * was written), and the file position is advanced by this number. It IS an
	 * error if this number is smaller than the number of bytes requested. For
	 * disk files, this indicates that the disk is full. For streams, this
	 * indicates the stream was terminated by the remote host before all the data
	 * was transferred.
	 *
	 * On error, -1 is returned, and the new file position is undefined. This can
	 * happen if fileDescriptor is invalid, if part of the buffer is invalid, or
	 * if a network stream has already been terminated by the remote host.
	 */
	private int handleWrite(int fileDescriptor, int vAddress, int count) {
		if (fileDescriptor > (maxDesc - 1) || fileDescriptor < 0 || openFiles[fileDescriptor] == null) {
			return -1;
		}

		int amountWritten = 0; //Ammount read
		int amountRead = 0;

		byte[] tempBuffer = new byte[count];     //temp buffer

		while (amountRead < count) {
			amountRead += readVirtualMemory(vAddress, tempBuffer);
			amountWritten += openFiles[fileDescriptor].write(tempBuffer, 0, (count - amountWritten));
		}

		if (amountWritten < count) {
			return -1;
		}

		return amountWritten;
	}

	private int handleClose(int fileDescriptor) {
		if (fileDescriptor > (maxDesc - 1) || fileDescriptor < 0 || openFiles[fileDescriptor] == null) {
			return -1;
		}
		openFiles[fileDescriptor].close();
		openFiles[fileDescriptor] = null;

		return 0;
	}

	private int handleUnlink(int address) {
		String fileName = readVirtualMemoryString(address, 256);    //Load address
		if (fileName == null) {
			return -1; //file you are trying to remove does not exist
		}

		ThreadedKernel.fileSystem.remove(fileName);

		return 0;
	}
	private static final int syscallHalt = 0,
		syscallExit = 1,
		syscallExec = 2,
		syscallJoin = 3,
		syscallCreate = 4,
		syscallOpen = 5,
		syscallRead = 6,
		syscallWrite = 7,
		syscallClose = 8,
		syscallUnlink = 9;

	/**
	 * Handle a syscall exception. Called by <tt>handleException()</tt>. The
	 * <i>syscall</i> argument identifies which syscall the user executed:
	 *
	 * <table>
	 * <tr><td>syscall#</td><td>syscall prototype</td></tr>
	 * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
	 * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
	 * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
	 *                                 </tt></td></tr>
	 * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
	 * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
	 * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
	 * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
	 *                                </tt></td></tr>
	 * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
	 *                                </tt></td></tr>
	 * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
	 * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
	 * </table>
	 *
	 * @param    syscall    the syscall number.
	 * @param    a0    the first syscall argument.
	 * @param    a1    the second syscall argument.
	 * @param    a2    the third syscall argument.
	 * @param    a3    the fourth syscall argument.
	 * @return    the value to be returned to the user.
	 */
	public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
		switch (syscall) {
			case syscallHalt:
				return handleHalt();
			case syscallExit:
				return handleExit(a0);
			case syscallExec:
				return handleExec(a0, a1, a2);
			case syscallJoin:
				return handleJoin(a0, a1);
			case syscallCreate:
				return handleCreate(a0);
			case syscallOpen:
				return handleOpen(a0);
			case syscallRead:
				return handleRead(a0, a1, a2);
			case syscallWrite:
				return handleWrite(a0, a1, a2);
			case syscallClose:
				return handleClose(a0);
			case syscallUnlink:
				return handleUnlink(a0);
			default:
				Lib.debug(dbgProcess, "Unknown syscall " + syscall);
				Lib.assertNotReached("Unknown system call!");
		}
		return 0;
	}

	/**
	 * Handle a user exception. Called by
	 * <tt>UserKernel.exceptionHandler()</tt>. The
	 * <i>cause</i> argument identifies which exception occurred; see the
	 * <tt>Processor.exceptionZZZ</tt> constants.
	 *
	 * @param    cause    the user exception that occurred.
	 */
	public void handleException(int cause) {
		Processor processor = Machine.processor();

		switch (cause) {
			case Processor.exceptionSyscall:
				int result = handleSyscall(processor.readRegister(Processor.regV0),
					processor.readRegister(Processor.regA0),
					processor.readRegister(Processor.regA1),
					processor.readRegister(Processor.regA2),
					processor.readRegister(Processor.regA3));
				processor.writeRegister(Processor.regV0, result);
				processor.advancePC();
				break;
			case Processor.exceptionAddressError:
			case Processor.exceptionBusError:
			case Processor.exceptionIllegalInstruction:
			case Processor.exceptionOverflow:
			case Processor.exceptionReadOnly:
			case Processor.exceptionTLBMiss:
			case Processor.exceptionPageFault:
				// Lib.debug("PageFault on address " + processor.readRegister(Processor.regBadVAddr));
				handleExit(-1);
				break;
			//System.err.println("Fatal Exception:"+ Processor.exceptionNames[cause]);
			//  break;
			default:
				Lib.debug(dbgProcess, "Unexpected exception: "
					+ Processor.exceptionNames[cause]);
				Lib.assertNotReached("Unexpected exception");
				handleExit(-1);
		}
	}

	/**
	 * Return first avaliable file descriptor.
	 */
	protected int getFileDescriptor() {
		for (int i = 0; i < maxDesc; i++) {
			if (openFiles[i] == null) {
				return i;
			}
		}
		return -1; //Non-open
	}

	public int getPid() {
		return pid;
	}


	/** List of file descriptors*/
	protected OpenFile[] openFiles;
	protected String[] openFileNames;
	protected final int maxDesc = 16;//max descriptors
	/** The program being run by this process. */
	protected Coff coff;
	/** This process's page table. */
	protected TranslationEntry[] pageTable;
	/** The number of contiguous pages occupied by the program. */
	protected int numPages;
	/** The number of pages in the program's stack. */
	protected final int stackPages = 8;
	/** Process ID (PID) information **/
	private int PID;
	private static int currentnextPID = 0;
	private int initialPC, initialSP;
	private int argc, argv;
	public static int processesRunning = 0;
	public static int currentPID = 0;
	public static Lock pidLock = new Lock();
	//private ArrayList<OpenFile> descriptionTable = new ArrayList<OpenFile>(16);
	//we need hashmaps to keep the information..
	private HashMap<Integer, UserProcess> children = new HashMap<Integer, UserProcess>();

	protected HashMap<Integer, Integer> childExitStatus = new HashMap<Integer, Integer>();
	protected UserProcess Parent;
	private int pid;
	private static final int pageSize = Processor.pageSize;
	private static final char dbgProcess = 'a';
	private static final OpenFile stdout = UserKernel.console.openForWriting();
	private static final OpenFile stdin = UserKernel.console.openForReading();
	public Lock lck = new Lock();
	public Condition2 childExit = new Condition2(lck);
}
